/*
  This file is part of Subsonic.
    Subsonic is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    Subsonic is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
    Copyright 2015 (C) Scott Jackson
*/

package net.nullsum.audinaut.view;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver;

import net.nullsum.audinaut.R;

import static androidx.recyclerview.widget.RecyclerView.OnScrollListener;

public class FastScroller extends LinearLayout {
    private static final String TAG = FastScroller.class.getSimpleName();
    private static final int BUBBLE_ANIMATION_DURATION = 100;
    private final ScrollListener scrollListener = new ScrollListener();
    private TextView bubble;
    private View handle;
    private RecyclerView recyclerView;
    private int height;
    private int visibleRange = -1;
    private RecyclerView.Adapter adapter;
    private AdapterDataObserver adapterObserver;
    private boolean visibleBubble = true;
    private boolean hasScrolled = false;

    private ObjectAnimator currentAnimator = null;

    public FastScroller(final Context context, final AttributeSet attrs, final int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initialise(context);
    }

    public FastScroller(final Context context) {
        super(context);
        initialise(context);
    }

    public FastScroller(final Context context, final AttributeSet attrs) {
        super(context, attrs);
        initialise(context);
    }

    private void initialise(Context context) {
        setOrientation(HORIZONTAL);
        setClipChildren(false);
        LayoutInflater inflater = LayoutInflater.from(context);
        inflater.inflate(R.layout.fast_scroller, this, true);
        bubble = findViewById(R.id.fastscroller_bubble);
        handle = findViewById(R.id.fastscroller_handle);
        bubble.setVisibility(INVISIBLE);
        setVisibility(GONE);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        height = h;
        visibleRange = -1;
    }

    @Override
    public boolean onTouchEvent(@NonNull MotionEvent event) {
        final int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                if (event.getX() < (handle.getX() - 30)) {
                    return false;
                }

                if (currentAnimator != null)
                    currentAnimator.cancel();
                if (bubble.getVisibility() == INVISIBLE) {
                    if (visibleBubble) {
                        showBubble();
                    }
                } else if (!visibleBubble) {
                    hideBubble();
                }
                handle.setSelected(true);
            case MotionEvent.ACTION_MOVE:
                setRecyclerViewPosition(event.getY());
                return true;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                handle.setSelected(false);
                hideBubble();
                return true;
        }
        return super.onTouchEvent(event);
    }

    public void attachRecyclerView(RecyclerView recyclerView) {
        this.recyclerView = recyclerView;
        recyclerView.addOnScrollListener(scrollListener);
        registerAdapter();
        visibleRange = -1;
    }

    public boolean isAttached() {
        return recyclerView != null;
    }

    private void setRecyclerViewPosition(float y) {
        if (recyclerView != null) {
            if (recyclerView.getChildCount() == 0) {
                return;
            }

            int itemCount = recyclerView.getAdapter().getItemCount();
            float proportion = getValueInRange(1f, y / (float) height);

            float targetPosFloat = getValueInRange(itemCount - 1, proportion * (float) itemCount);
            int targetPos = (int) targetPosFloat;

            // Immediately make sure that the target is visible
            LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
            // layoutManager.scrollToPositionWithOffset(targetPos, 0);
            View firstVisibleView = recyclerView.getChildAt(0);

            // Calculate how far through this position we are
            int columns = Math.round(recyclerView.getWidth() / firstVisibleView.getWidth());
            int firstVisiblePosition = recyclerView.getChildAdapterPosition(firstVisibleView);
            int remainder = (targetPos - firstVisiblePosition) % columns;
            float offsetPercentage = (targetPosFloat - targetPos + remainder) / columns;
            if (offsetPercentage < 0) {
                offsetPercentage = 1 + offsetPercentage;
            }
            int firstVisibleHeight = firstVisibleView.getHeight();
            if (columns > 1) {
                firstVisibleHeight += (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, GridSpacingDecoration.SPACING, firstVisibleView.getResources().getDisplayMetrics());
            }
            int offset = (int) (offsetPercentage * firstVisibleHeight);

            layoutManager.scrollToPositionWithOffset(targetPos, -offset);
            onUpdateScroll(1, 1);

            try {
                String bubbleText = null;
                RecyclerView.Adapter adapter = recyclerView.getAdapter();
                if (adapter instanceof BubbleTextGetter) {
                    bubbleText = ((BubbleTextGetter) adapter).getTextToShowInBubble(targetPos);
                }

                if (bubbleText == null) {
                    visibleBubble = false;
                    bubble.setVisibility(View.INVISIBLE);
                } else {
                    bubble.setText(bubbleText);
                    bubble.setVisibility(View.VISIBLE);
                    visibleBubble = true;
                }
            } catch (Exception e) {
                Log.e(TAG, "Error getting text for bubble", e);
            }
        }
    }

    private float getValueInRange(float max, float value) {
        float minimum = Math.max((float) 0, value);
        return Math.min(minimum, max);
    }

    private void setBubbleAndHandlePosition(float y) {
        int bubbleHeight = bubble.getHeight();
        int handleHeight = handle.getHeight();
        handle.setY(getValueInRange(height - handleHeight, (int) (y - handleHeight / 2)));
        bubble.setY(getValueInRange(height - bubbleHeight - handleHeight / 2, (int) (y - bubbleHeight)));
    }

    private void showBubble() {
        bubble.setVisibility(VISIBLE);
        if (currentAnimator != null)
            currentAnimator.cancel();
        currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 0f, 1f).setDuration(BUBBLE_ANIMATION_DURATION);
        currentAnimator.start();
    }

    private void hideBubble() {
        if (currentAnimator != null)
            currentAnimator.cancel();
        currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 1f, 0f).setDuration(BUBBLE_ANIMATION_DURATION);
        currentAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                bubble.setVisibility(INVISIBLE);
                currentAnimator = null;
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                super.onAnimationCancel(animation);
                bubble.setVisibility(INVISIBLE);
                currentAnimator = null;
            }
        });
        currentAnimator.start();
    }

    private void registerAdapter() {
        RecyclerView.Adapter newAdapter = recyclerView.getAdapter();
        if (newAdapter != adapter) {
            unregisterAdapter();
        }

        if (newAdapter != null) {
            adapterObserver = new AdapterDataObserver() {
                @Override
                public void onChanged() {
                    visibleRange = -1;
                }

                @Override
                public void onItemRangeChanged(int positionStart, int itemCount) {
                    visibleRange = -1;
                }

                @Override
                public void onItemRangeInserted(int positionStart, int itemCount) {
                    visibleRange = -1;
                }

                @Override
                public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
                    visibleRange = -1;
                }

                @Override
                public void onItemRangeRemoved(int positionStart, int itemCount) {
                    visibleRange = -1;
                }
            };
            newAdapter.registerAdapterDataObserver(adapterObserver);
            adapter = newAdapter;
        }
    }

    private void unregisterAdapter() {
        if (adapter != null) {
            adapter.unregisterAdapterDataObserver(adapterObserver);
            adapter = null;
            adapterObserver = null;
        }
    }

    private void onUpdateScroll(int dx, int dy) {
        if (recyclerView.getWidth() == 0) {
            return;
        }
        registerAdapter();

        View firstVisibleView = recyclerView.getChildAt(0);
        if (firstVisibleView == null) {
            return;
        }
        int firstVisiblePosition = recyclerView.getChildAdapterPosition(firstVisibleView);

        int itemCount = recyclerView.getAdapter().getItemCount();
        int columns = Math.round(recyclerView.getWidth() / firstVisibleView.getWidth());
        if (visibleRange == -1) {
            visibleRange = recyclerView.getChildCount();
        }

        // Add the percentage of the item the user has scrolled past already
        float pastFirst = -firstVisibleView.getY() / firstVisibleView.getHeight() * columns;
        float position = firstVisiblePosition + pastFirst;

        // Scale this so as we move down the visible range gets added to position from 0 -> visible range
        float scaledVisibleRange = position / (float) (itemCount - visibleRange) * visibleRange;
        position += scaledVisibleRange;

        float proportion = position / itemCount;
        setBubbleAndHandlePosition(height * proportion);

        if ((visibleRange * 2) < itemCount) {
            if (!hasScrolled && (dx > 0 || dy > 0)) {
                setVisibility(View.VISIBLE);
                hasScrolled = true;
                recyclerView.setVerticalScrollBarEnabled(false);
            }
        } else if (hasScrolled) {
            setVisibility(View.GONE);
            hasScrolled = false;
            recyclerView.setVerticalScrollBarEnabled(true);
        }
    }

    public interface BubbleTextGetter {
        String getTextToShowInBubble(int position);
    }

    private class ScrollListener extends OnScrollListener {
        @Override
        public void onScrolled(RecyclerView rv, int dx, int dy) {
            onUpdateScroll(dx, dy);
        }
    }
}
